﻿using System;
using System.Diagnostics;
using System.Reflection;
using Moq;

namespace UnitTesting.Fixturing.Moq
{
	class FixtureMock<T> : Mock<T> where T : class
	{
		readonly IValueProvider _valueProvider;
		readonly IFreezable _freezable;

		public override DefaultValue DefaultValue
		{
			get { return base.DefaultValue; }
			set
			{
				if (GetCallingAssemlyName() == "Moq")
				{
					return;
				}
				SetDefaultValue(value);
			}
		}

		static string GetCallingAssemlyName()
		{
			// ReSharper disable PossibleNullReferenceException
			return new StackTrace().GetFrame(2).GetMethod().DeclaringType.Assembly.GetName().Name;
			// ReSharper restore PossibleNullReferenceException
		}

		void SetDefaultValue(DefaultValue value)
		{
			var fixtureMockDefaultValue = (FixtureMockDefaultValue) value;
			switch (fixtureMockDefaultValue)
			{
				case FixtureMockDefaultValue.Empty:
				case FixtureMockDefaultValue.Mock:
					base.DefaultValue = value;
					return;
				case FixtureMockDefaultValue.Fixture:
					SetDefaultValueImp(value);
					return;
				default:
					throw new ArgumentOutOfRangeException(fixtureMockDefaultValue.ToString());
			}
		}

		void SetDefaultValueImp(DefaultValue defaultValue)
		{
			SetField("defaultValue", defaultValue);
			SetField("defaultValueProvider", CreateFixtureDefaultValueProvider(this, _valueProvider, _freezable));
		}

		void SetField(string name, object value)
		{
			// ReSharper disable PossibleNullReferenceException
			typeof (Mock).GetField(name, BindingFlags.NonPublic | BindingFlags.Instance)
				// ReSharper restore PossibleNullReferenceException
				.SetValue(this, value);
		}

		internal virtual object CreateFixtureDefaultValueProvider(Mock fixtureMock, IValueProvider valueProvider, IFreezable freezable)
		{
			return new FixtureDefaultValueProvider(fixtureMock, valueProvider, freezable).Object;
		}

		internal FixtureMock(IValueProvider valueProvider, IFreezable freezable, MockBehavior behavior, params object[] args)
			: base(behavior, args)
		{
			_valueProvider = valueProvider;
			_freezable = freezable;
		}
	}
}